/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_zml.htm
 *
 * Zhiqim Zml is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.zml.loader;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.HashMap;
import java.util.List;

import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.model.maps.HashMapSV;
import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.kernel.util.Watchs;
import org.zhiqim.zml.ZmlConstants;
import org.zhiqim.zml.ZmlEngine;

/**
 * ZML文件监视，所有包含*.zml文件的文件夹，
 * 1.采用WatchService进行监视，只监视修改事件
 * 2.增加和删除时修改事件也会触发，统一检查文件是否存在来判断是增加还是删除
 *
 * @version v1.0.0 @author zouzhigang 2016-7-13 新建与整理
 */
public class FileZmlWatcher extends Thread implements ZmlConstants
{
    private static final Log log = LogFactory.getLog(FileZmlWatcher.class);
    
    private boolean isRunning;
    
    private final ZmlEngine engine;
    private final String pathPrefix;
    private final HashMapSV<Long> fileMap;
    private final HashMapSV<WatchKey> dirMap;
    private final HashMap<WatchKey, String> keyMap;
    private final WatchService watcher;
    
    public FileZmlWatcher(ZmlEngine engine, String rootPath)
    {
        this.engine = engine;
        this.pathPrefix = rootPath;
        this.fileMap = new HashMapSV<>();
        this.dirMap = new HashMapSV<>();
        this.keyMap = new HashMap<>();
        this.watcher = Watchs.getWatchService();
        
        this.start();
    }
    
    public long getLastModified(String path)
    {
        if (fileMap.containsKey(path))
        {//1.如果有数据，则直接返回
            return fileMap.get(path);
        }
        
        String dirPath = Files.getFileDirPath(path, "/");
        File dir = new File(pathPrefix + dirPath);
        if (!dir.exists() || !dir.isDirectory() || !dir.canRead())
        {//2.检查文件夹
            WatchKey key = dirMap.get(dirPath);
            if (key != null)
            {//取消监视
                keyMap.remove(key);
                dirMap.remove(dirPath);
                key.cancel();
                key = null;
            }
            
            fileMap.remove(path);
            return -1;
        }
        
        File file = new File(pathPrefix + path);
        if (!file.exists() || !file.isFile() || !file.canRead())
        {//3.文件不存在，返回-1
            fileMap.remove(path);
            return -1;
        }
        
        if (!dirMap.containsKey(dirPath))
        {//4.把文件夹加入到监视中去
            synchronized (dirMap)
            {
                if (!dirMap.containsKey(dirPath))
                {
                    try
                    {
                        Path dPath = dir.toPath();
                        WatchKey key = dPath.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
                        dirMap.put(dirPath, key);
                        keyMap.put(key, dirPath);
                    }
                    catch (IOException e)
                    {
                        log.error("监视文件夹线程添加监视时异常", e);
                    }
                }
            }
        }
        
        //5.保存文件最后更新时间并返回
        long fileModified = file.lastModified();
        fileMap.put(path, fileModified);
        return fileModified;
    }

    public void run()
    {
        isRunning = true;
        while(isRunning)
        {
            try
            {
                WatchKey key = watcher.take();
                
                //1.找到文件夹
                String dirPath = keyMap.get(key);
                
                //2.读取事件列表
                List<WatchEvent<?>> eventList = key.pollEvents();
                for (WatchEvent<?> event : eventList)
                {
                    Path path = (Path)event.context();
                    String fileName = path.toFile().getName();
                    String filePath = dirPath + "/" + fileName;
                    if (!Validates.isMatch(filePath, engine.getPatterns()))
                        continue;
                    
                    File file = new File(pathPrefix + filePath);
                    if (!file.exists())
                        fileMap.remove(filePath);
                    else
                        fileMap.put(filePath, file.lastModified());
                }
                
                //3.重新设置监视
                if (!key.reset())
                {
                    keyMap.remove(key);
                    dirMap.remove(dirPath);
                    key.cancel();
                }
            }
            catch (InterruptedException e)
            {
                log.error("监视文件夹线程被中断，退出", e);
                isRunning = false;
            }
        }
    }
}
